/**
 * MIT License
 * Copyright (c) 2018 yadong.zhang
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.zyd.shiro.framework.config;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.DelegatingFilterProxy;

import com.zyd.shiro.business.service.ShiroService;
import com.zyd.shiro.business.shiro.realm.MyShiroRealm;
import com.zyd.shiro.framework.property.RedisProperties;

/**
 * Shiro配置类
 *
 * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0
 * @website https://www.zhyd.me
 * @date 2018/4/24 14:37
 * @since 1.0
 */
@Configuration
@Order(-1)
public class ShiroConfig {

    @Autowired
    private ShiroService shiroService;
    @Autowired
    private RedisProperties redisProperties;
    
    // cas server地址 
//    public static final String casServerUrlPrefix = "https://cas.lcy.com:8443";
    public static final String casServerUrlPrefix = "http://cas.lcy.com:9000";
    // Cas登录页面地址 
    public static final String casLoginUrl = casServerUrlPrefix + "/login"; 
    // Cas登出页面地址 
    public static final String casLogoutUrl = casServerUrlPrefix + "/logout"; 
    // 当前工程对外提供的服务地址 
    public static final String shiroServerUrlPrefix = "http://www.qiandu.com:8081";
    // casFilter UrlPattern 
    public static final String casFilterUrlPattern = "/index";
    // 登录地址 
    public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
    // 登出地址（casserver启用service跳转功能，需在webapps\cas\WEB-INF\cas.properties文件中启用cas.logout.followServiceRedirects=true） 
    public static final String logoutUrl = casLogoutUrl+"?service="+loginUrl;
    // 登录成功地址 
    //  public static final String loginSuccessUrl = "/index"; 
    // 权限认证失败跳转地址 
    public static final String unauthorizedUrl = "/error/403.html"; 

    
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myShiroCasRealm());
        // 用户授权/认证信息Cache, 采用redis 缓存
        securityManager.setCacheManager(redisCacheManager());
        
        // 自定义session管理 使用redis
//        securityManager.setSessionManager(sessionManager());
        
        // 指定 SubjectFactory,如果要实现cas的remember me的功能，需要用到下面这个CasSubjectFactory，并设置到securityManager的subjectFactory中
        securityManager.setSubjectFactory(new CasSubjectFactory());
        return securityManager;
    }

    @Bean
    public MyShiroRealm myShiroCasRealm() {
        MyShiroRealm realm = new MyShiroRealm();
        // cas登录服务器地址前缀
        realm.setCasServerUrlPrefix(ShiroConfig.casServerUrlPrefix);
        // 客户端回调地址，登录成功后的跳转地址(自己的服务地址)
        realm.setCasService(ShiroConfig.shiroServerUrlPrefix + ShiroConfig.casFilterUrlPattern);
        // 登录成功后的默认角色，此处默认为user角色
//    	realm.setDefaultRoles("testUser");
        return realm;
    }
    
    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
    
    /**
     * shiro session的管理
     */
    /*@Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }*/

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    /*@Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }*/

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(redisProperties.getHost());
        redisManager.setPort(redisProperties.getPort());
        redisManager.setDatabase(redisProperties.getDatabase());
        redisManager.setTimeout(redisProperties.getTimeout());
        redisManager.setPassword(redisProperties.getPassword());
        return redisManager;
    }
    
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager){
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(securityManager);
        return bean;
    }

    /** 
     * 该类可以保证实现了org.apache.shiro.util.Initializable接口的shiro对象的init或者是destory方法被自动调用， 
     * 而不用手动指定init-method或者是destory-method方法 
     * 注意：如果使用了该类，则不需要手动指定初始化方法和销毁方法，否则会出错 
     * @return 
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /** 
     * 下面两个配置主要用来开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; 
     * @return 
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    
    /** 
     * 注册单点登出的listener 
     * 当有session被注销的时候，触发将sessionMappingStorage中对应的sessionId中的数据删除
     * @return 
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)// 优先级需要高于Cas的Filter 
    public ServletListenerRegistrationBean<?> singleSignOutHttpSessionListener(){ 
      ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean(); 
      bean.setListener(new SingleSignOutHttpSessionListener()); 
      bean.setEnabled(true); 
      return bean; 
    }

   
	/* 注册单点登出filter
	 * 拿着ticket回跳到客户端后，客户端一看地址的请求参数有ticket的时候，singleSignOutFilter注册带有ticket作为id的session到sessionMappingStorage
	 * */
    @Bean
    public FilterRegistrationBean singleSignOutFilter(){ 
    	FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setName("singleSignOutFilter");
        filterRegistration.setFilter(new SingleSignOutFilter());
        filterRegistration.addUrlPatterns(casFilterUrlPattern);
        filterRegistration.setOrder(2);
        filterRegistration.setEnabled(true);
    	return filterRegistration;
    }

    /**
     * ticket认证Filter
     * @param
     * @return
     * @author luochangyong
     * @date 2019/10/18 17:17
     */
   /* @Bean
    public FilterRegistrationBean filterValidationRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
        Map<String,String>  initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", casServerUrlPrefix);
        initParameters.put("serverName", shiroServerUrlPrefix);
        initParameters.put("useSession", "true");
        registration.setInitParameters(initParameters);
        //set mapping url
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }

    *//**
     * wraper过滤器
     * @param
     * @return org.springframework.boot.web.servlet.FilterRegistrationBean
     * @author luochangyong
     * @date 2019/10/18 17:19
     *//*
    @Bean
    public FilterRegistrationBean filterWrapperRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new HttpServletRequestWrapperFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }*/

    /**
     * 注册DelegatingFilterProxy（Shiro）
     */
    @Bean
    public FilterRegistrationBean delegatingFilterProxy() {
      FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
      filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
      // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
      filterRegistration.addInitParameter("targetFilterLifecycle", "true");
      filterRegistration.addUrlPatterns("/*");
      filterRegistration.setEnabled(true);
      return filterRegistration;
    }
    
    /** 
     * CAS过滤器 
     * @return 
     */
    @Bean(name = "casFilter") 
    public CasFilter getCasFilter() { 
      CasFilter casFilter = new CasFilter(); 
      casFilter.setName("casFilter"); 
      casFilter.setEnabled(true); 
      // 登录失败后跳转的URL，也就是 Shiro 执行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer验证tiket 
      casFilter.setFailureUrl(loginUrl);// 我们选择认证失败后再打开登录页面
      casFilter.setLoginUrl(loginUrl);
      return casFilter; 
    } 
    
    /** 
     * 使用工厂模式，创建并初始化ShiroFilter 
     * @param securityManager 
     * @param casFilter 
     * @return 
     */
    @Bean(name = "shiroFilter") 
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter) {
      ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 
      // 必须设置 SecurityManager 
      shiroFilterFactoryBean.setSecurityManager(securityManager); 
      // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 
      shiroFilterFactoryBean.setLoginUrl(loginUrl);
      /* 
       * 登录成功后要跳转的连接，不设置的时候，会默认跳转到前一步的url 
       * 比如先在浏览器中输入了http://localhost:8080/userlist,但是现在用户却没有登录，于是会跳转到登录页面，等登录认证通过后， 
       * 页面会再次自动跳转到http://localhost:8080/userlist页面而不是,,,,登录成功后的index页面
       * 建议不要设置这个字段 
       */ 
//      shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl); 
        
      // 设置无权限访问页面 
      shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl); 
      /* 
       * 添加casFilter到shiroFilter中，注意，casFilter需要放到shiroFilter的前面， 
       * 从而保证程序在进入shiro的login登录之前就会进入单点认证 
       */ 
      Map<String, Filter> filters = new LinkedHashMap<>();
      filters.put("casFilter", casFilter); 
        
      // logout已经被单点登录的logout取代 
      // filters.put("logout",logoutFilter()); 
      shiroFilterFactoryBean.setFilters(filters); 
      
      shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroService.loadFilterChainDefinitions()); 
      
      return shiroFilterFactoryBean; 
    }

    @Bean
    public FilterRegistrationBean testFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new MyFilter());//添加过滤器
        registration.addUrlPatterns("/*");//设置过滤路径，*//**//*所有路径
        registration.addInitParameter("name", "value");//添加默认参数
        registration.setName("MyFilter");//设置优先级
        registration.setOrder(1);//设置优先级
        return registration;
    }

    private class MyFilter implements Filter {
        @Override
        public void destroy() {
        }
        @Override
        public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain
                filterChain)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) srequest;
            //打印请求Url
            System.out.println("+++" + request.getRequestURI() + ",ticket:" + CommonUtils.safeGetParameter(request, "ticket") +",logoutRequest:" + CommonUtils.safeGetParameter(request, "logoutRequest") + ",session:" + request.getSession(true).getId());
            filterChain.doFilter(srequest, sresponse);
        }
        @Override
        public void init(FilterConfig arg0) throws ServletException {
        }
    }

}
